#include "FBXUtility.h"
#include "Mesh.h"

#ifdef PSX_DEBUG
#pragma comment(lib,"fbxsdk-2014.1-mdd.lib")
#else
#pragma comment(lib,"fbxsdk-2014.1-md.lib")
#endif

namespace Pulse
{
	#define MAX_UV_SETS 25

	FBXUtility::FBXUtility( void )
	{
		m_pSDKManager = PSX_NULL;
		m_pScene = PSX_NULL;
	}

	FBXUtility::~FBXUtility( void )
	{
		Cleanup();
	}

	EErrorCode::Type FBXUtility::LoadScene( const CHAR *pFilename )
	{
		Cleanup();
		Initialize();

		SIZE_T strLen = PSX_StrLen( pFilename ) + 1;
		Char *pBuffer = new Char[strLen];
		String::ToMultiByte( pFilename, pBuffer, strLen );

		FbxImporter *pImporter = FbxImporter::Create( m_pSDKManager, "" );
		BOOL bStatus = pImporter->Initialize( pBuffer, -1, m_pSDKManager->GetIOSettings() );

		delete [] pBuffer;

		if ( !bStatus )
		{
			PSX_PushError( "Failed to load filename %s.", pFilename );
			return EErrorCode::INVALID_PARAMETER;
		}

		// Is FBX scene?
		if ( pImporter->IsFBX() )
		{
			// Set the import states. By default, the import states are always set to 
			// true. The code below shows how to change these states.
			FbxIOSettings *pSettings = m_pSDKManager->GetIOSettings();
			pSettings->SetBoolProp(IMP_FBX_MATERIAL,        true);
			pSettings->SetBoolProp(IMP_FBX_TEXTURE,         true);
			pSettings->SetBoolProp(IMP_FBX_LINK,            true);
			pSettings->SetBoolProp(IMP_FBX_SHAPE,           true);
			pSettings->SetBoolProp(IMP_FBX_GOBO,            true);
			pSettings->SetBoolProp(IMP_FBX_ANIMATION,       true);
			pSettings->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true);
		}

		// Import the scene
		bStatus = pImporter->Import( m_pScene );
		
		// Do some system conversions for DirectX left-handed coordinate system.
		if ( bStatus )
		{
			FbxAxisSystem leftAxisSystem( FbxAxisSystem::/*EUpVector::*/eYAxis, 
				FbxAxisSystem::/*EFrontVector::*/eParityOdd,
				FbxAxisSystem::eLeftHanded );

			FbxAxisSystem DXAxis( FbxAxisSystem::eDirectX ) ;

			//FbxAxisSystem::ECoordSystem coord = m_pScene->GetGlobalSettings().GetAxisSystem().GetCoorSystem();
			//leftAxisSystem.ConvertScene( m_pScene );
			DXAxis.ConvertScene( m_pScene );

		}

		// Convert unit system to meters
		FbxSystemUnit sceneSystemUnit = m_pScene->GetGlobalSettings().GetSystemUnit();
		if ( sceneSystemUnit != FbxSystemUnit::m )
		{
			const FbxSystemUnit::ConversionOptions lConversionOptions = {
				false, /* mConvertRrsNodes */
				true, /* mConvertAllLimits */
				true, /* mConvertClusters */
				true, /* mConvertLightIntensity */
				true, /* mConvertPhotometricLProperties */
				true  /* mConvertCameraClipPlanes */
			};
			FbxSystemUnit::m.ConvertScene( m_pScene, lConversionOptions );
		}

		// Destroy the importer. We don't need it anymore.
		pImporter->Destroy();

		return bStatus ? EErrorCode::OKAY : EErrorCode::INVALID_PARAMETER;
	}

	const FbxNode * FBXUtility::GetRootNode( void )
	{
		return m_pScene->GetRootNode();
	}

	const Char * FBXUtility::GetRootNodeName( void )
	{
		return m_pScene->GetRootNode()->GetName();
	}

	EErrorCode::Type FBXUtility::ProcessMesh( FbxNode *pMeshNode )
	{
		if ( pMeshNode == NULL )
		{
			PSX_PushError( "Invalid parameter. Must not be null." );
			return EErrorCode::INVALID_PARAMETER;
		}

		if ( pMeshNode->GetNodeAttribute() == NULL )
		{
			PSX_PushError( "No mesh attribute." );
			return EErrorCode::INVALID_PARAMETER;
		}

		FbxNodeAttribute *pAttribute = pMeshNode->GetNodeAttribute();
		FbxNodeAttribute::EType attributeType = pAttribute->GetAttributeType();

		if ( attributeType != FbxNodeAttribute::eMesh )
		{
			PSX_PushError( "Attribute is not a mesh type." );
			return EErrorCode::INVALID_PARAMETER;
		}

		Mesh mesh;
		FbxMesh *pMesh = pMeshNode->GetMesh();
		const int numPolygons = pMesh->GetPolygonCount();
		FbxLayerElementArrayTemplate<int> *pMaterialIndices = NULL;
		FbxGeometryElement::EMappingMode materialMappingMode = FbxGeometryElement::eNone;

		if ( pMesh->GetElementMaterial() )
		{
			pMaterialIndices = &pMesh->GetElementMaterial()->GetIndexArray();
			materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode();

			if ( pMaterialIndices && 
				( materialMappingMode == FbxGeometryElement::eByPolygon ||
				materialMappingMode == FbxGeometryElement::eAllSame ) )
			{
				// Set to the number of materials(num submeshes) used and 
				//	Count the faces for each material
				for ( int i = 0; i < numPolygons; ++i )
				{
					const int materialIndex = pMaterialIndices->GetAt(i);

					if ( mesh.GetNumSubMeshes() < (SIZE_T32)(materialIndex + 1) )
						mesh.ResizeSubMeshes( materialIndex + 1 );

					mesh.GetSubMesh(materialIndex)->numTriangles += 1;
				}

				// Set the offsets
				const int numMaterials = mesh.GetNumSubMeshes();
				int offset = 0;
				for ( int i = 0; i < numMaterials; ++i )
				{
					SubMesh *pSubMesh = mesh.GetSubMesh(i);
					pSubMesh->indicesOffset = offset;
					offset += pSubMesh->numTriangles * 3; // 3 vertices/triangle
					pSubMesh->numTriangles = 0; // Reset to 0. This will be used later below.
				}

				// offset should be equal to total triangles(numpolygons * 3).
				if ( numPolygons * 3 != offset )
				{
					PSX_PushError( "Number of polygons not equal to total offset." );
					return EErrorCode::MODEL_EXPORTER;
				}
			}
		}

		// IF no materials (submeshes) has been generated, then
		// all faces will use the same material
		if ( mesh.GetNumSubMeshes() == 0 )
		{
			mesh.ResizeSubMeshes( 1 );
			//mesh.GetSubMesh(0);
		}

		BOOL bHasNormals = pMesh->GetElementNormalCount() > 0;
		BOOL bHasUV = pMesh->GetElementUVCount() > 0;
		FbxGeometryElement::EMappingMode normalMappingMode = FbxGeometryElement::eNone;
		FbxGeometryElement::EMappingMode UVMappingMode = FbxGeometryElement::eNone;
		BOOL bALlocateByControlPoints = TRUE;

		if ( bHasNormals )
		{
			normalMappingMode = pMesh->GetElementNormal()->GetMappingMode();
		
			if ( normalMappingMode == FbxGeometryElement::eNone )
				bHasNormals = FALSE;

			if ( bHasNormals && normalMappingMode != FbxGeometryElement::eByControlPoint )
				bALlocateByControlPoints = FALSE;
		}

		if ( bHasUV )
		{
			UVMappingMode = pMesh->GetElementUV()->GetMappingMode();

			if ( UVMappingMode == FbxGeometryElement::eNone )
				bHasUV = FALSE;

			if ( bHasUV && UVMappingMode != FbxGeometryElement::eByControlPoint )
				bALlocateByControlPoints = FALSE;
		}

		// Allocate vertices by control points or triangles?
		const SIZE_T32 numVertices = bALlocateByControlPoints ? pMesh->GetControlPointsCount() : numPolygons * 3;

		mesh.ResizeVertices( numVertices );
		mesh.ResizeIndices( numPolygons * 3 );

		if ( bHasNormals )
			mesh.SetNormalsEnabled( TRUE );

		FbxStringList uvNames;
		pMesh->GetUVSetNames( uvNames );
		if ( bHasUV && uvNames.GetCount() )
		{
			int numUVSets = uvNames.GetCount();
			mesh.ResizeUVSets( numUVSets );

			for ( int i = 0; i < numUVSets; ++i )
				mesh.GetUVSet( i )->name = uvNames[i];
		}

		// Populate the vertex arrays
		const FbxVector4 *pControlPoints = pMesh->GetControlPoints();

		if ( bALlocateByControlPoints )
		{
			// Get normal and UVs if available

			for ( SIZE_T32 i = 0; i < numVertices; ++i )
			{
				FbxVector4 currentVertex;
				Vector3 *pVertexToFill = &mesh.GetVertices()[i];

				currentVertex = pControlPoints[i];
				pVertexToFill->x = (FLOAT)currentVertex[0];
				pVertexToFill->y = (FLOAT)currentVertex[1];
				pVertexToFill->z = (FLOAT)currentVertex[2];
			}

			if ( bHasNormals )
			{
				const FbxGeometryElementNormal *pNormalElement = pMesh->GetElementNormal();
				FbxVector4 currentNormal;
				
				for ( SIZE_T32 i = 0; i < numVertices; ++i )
				{
					{
						int normalIndex = i;

						if ( pNormalElement->GetReferenceMode() == FbxLayerElement::eIndexToDirect )
							normalIndex = pNormalElement->GetIndexArray().GetAt(i);
					
						Vector3 *pNormalToFill = &mesh.GetNormals()[i];
						currentNormal = pNormalElement->GetDirectArray().GetAt(normalIndex);

						pNormalToFill->x = (FLOAT)currentNormal[0]; 
						pNormalToFill->y = (FLOAT)currentNormal[1]; 
						pNormalToFill->z = (FLOAT)currentNormal[2]; 
					
					}
				}
			}

			if ( bHasUV )
			{
				SIZE_T32 UVIndex;
				FbxVector2 currentUV;
				const FbxGeometryElementUV *pUVElement = PSX_NULL;

				for ( SIZE_T i = 0; i < mesh.GetNumUVSets(); ++i )
				{
					UVIndex = (SIZE_T32)i;
					pUVElement = pMesh->GetElementUV((int)i);

					for ( SIZE_T32 j = 0; j < numVertices; ++i )
					{

						if ( pUVElement->GetReferenceMode() == FbxLayerElement::eIndexToDirect )
							UVIndex = pUVElement->GetIndexArray().GetAt(j);
						
						Vector2 *pUVToFill = &mesh.GetUVSet(j)->UVs[i];
						currentUV = pUVElement->GetDirectArray().GetAt(UVIndex);

						pUVToFill->x = (FLOAT)currentUV[0];
						pUVToFill->y = (FLOAT)currentUV[1];
					}
				}
			}
		}

		// Fill the indices.
		int vertexCount = 0;
		for ( int polygonIndex = 0; polygonIndex < numPolygons; ++polygonIndex )
		{
			// Get material index if available
			int materialIndex = 0;
			if ( pMaterialIndices && materialMappingMode == FbxGeometryElement::eByPolygon )
				materialIndex = pMaterialIndices->GetAt(polygonIndex);

			// Determine where to store the vertex attribute index
			const int indexOffset = mesh.GetSubMesh(materialIndex)->indicesOffset +
				mesh.GetSubMesh(materialIndex)->numTriangles * 3;

			// Three vertices per triangle
			for ( int vertexIndex = 0; vertexIndex < 3; ++vertexIndex )
			{
				const int controlPointIndex = pMesh->GetPolygonVertex(polygonIndex, vertexIndex);
			
				if ( bALlocateByControlPoints )
				{
					mesh.GetIndices()[indexOffset + vertexIndex] = controlPointIndex;
				}
				else
				{
					// polygon vertex has the elements jumbled with indices per polygon.
					// we have to rearrange them so that each index maps to the same index in each
					// elements.

					mesh.GetIndices()[indexOffset + vertexIndex] = vertexCount;

					FbxVector4 currentVertex = pControlPoints[controlPointIndex];
					Vector3 *pVertexToFill = &mesh.GetVertices()[vertexCount];
					pVertexToFill->x = (FLOAT)currentVertex[0];
					pVertexToFill->y = (FLOAT)currentVertex[1];
					pVertexToFill->z = (FLOAT)currentVertex[2];

					if ( bHasNormals )
					{
						FbxVector4 currentNormal;
						pMesh->GetPolygonVertexNormal( polygonIndex, vertexIndex, currentNormal );
						Vector3 *pNormalToFill = &mesh.GetNormals()[vertexCount];
					
						pNormalToFill->x = (FLOAT)currentNormal[0];
						pNormalToFill->y = (FLOAT)currentNormal[1];
						pNormalToFill->z = (FLOAT)currentNormal[2];
					}

					if ( bHasUV )
					{
						SIZE_T UVIndex;
						FbxVector2 currentUV;
						Char buffer[256];

						for ( SIZE_T i = 0; i < mesh.GetNumUVSets(); ++i )
						{
							UVIndex = i;
							String::ToMultiByte( mesh.GetUVSet((UPOS_T32)i)->name.GetBuffer(), buffer, 256 );
							pMesh->GetPolygonVertexUV( polygonIndex, vertexIndex, buffer, currentUV );

							Vector2 *pUVToFill = &mesh.GetUVSet((UPOS_T32)i)->UVs[vertexCount];

							pUVToFill->x = (FLOAT)currentUV[0];
							pUVToFill->y = (FLOAT)currentUV[1];
						}
					}
				}
				++vertexCount;
			}
			++mesh.GetSubMesh(materialIndex)->numTriangles;
		}

		// We should now have a complete mesh. 
		// Export as geometry file
		String meshName = pMesh->GetName();
		if ( meshName.GetLength() == 0 )
		{
			// TODO: Make sure the name doesn't exist.
			meshName = PSX_String("poop.xml");
		}
		else
			meshName += PSX_String(".xml");

		// This might be handy if we move this inside the mesh class.
		GeometryFileWriter &writer = m_fileWriter;
		writer.StartWrite(meshName.GetBuffer());

		// Write position
		for ( SIZE_T32 i = 0; i < mesh.GetNumVertices(); ++i )
			writer.AddPosition( &mesh.GetVertices()[i] );

		// Write Indices
		for ( SIZE_T32 i = 0; i < mesh.GetNumIndices(); ++i )
			writer.AddIndex( mesh.GetIndices()[i] );

		// Write normals
		if ( mesh.IsNormalsEnabled() )
		{
			for ( SIZE_T32 i = 0; i < mesh.GetNumVertices(); ++i )
				writer.AddNormal( &mesh.GetNormals()[i] );
		}

		// Write UVS
		if ( mesh.GetNumUVSets() )
		{
			UVSet *pUVDiffuse = mesh.GetUVSet(0);
			for ( SIZE_T i = 0; i < pUVDiffuse->UVs.GetSize(); ++i )
			{
				writer.AddTextureDiffuse( &pUVDiffuse->UVs[i] );
			}
		}

		// Write submeshes
		for ( SIZE_T32 i = 0; i < mesh.GetNumSubMeshes(); ++i )
		{
			SubMesh *pSubMesh = mesh.GetSubMesh(i);
			writer.AddSubGeometry( pSubMesh->indicesOffset, pSubMesh->numTriangles );
		}

		writer.EndWrite();
		

		return EErrorCode::OKAY;
	}

	EErrorCode::Type FBXUtility::Initialize( void )
	{
		m_pSDKManager = FbxManager::Create();
		FbxIOSettings *pIOSettings = FbxIOSettings::Create( m_pSDKManager, IOSROOT );
		m_pSDKManager->SetIOSettings( pIOSettings );
		m_pScene = FbxScene::Create( m_pSDKManager, "" );

		return EErrorCode::OKAY;
	}

	void FBXUtility::Cleanup( void )
	{
		if ( m_pSDKManager ) 
		{
			m_pSDKManager->Destroy();
			m_pSDKManager = PSX_NULL;
			m_pScene = PSX_NULL;
		}
	}

	BOOL FBXUtility::TriangulateRecursive( FbxNode *pNode )
	{
		if ( pNode == NULL )
			return TRUE;

		FbxNodeAttribute *pAttribute = pNode->GetNodeAttribute();

		if ( pAttribute )
		{
			if ( pAttribute->GetAttributeType() == FbxNodeAttribute::eMesh ||
				pAttribute->GetAttributeType() == FbxNodeAttribute::eNurbs ||
				pAttribute->GetAttributeType() == FbxNodeAttribute::eNurbsSurface ||
				pAttribute->GetAttributeType() == FbxNodeAttribute::ePatch )
			{
				// Triangulate this node
				FbxGeometryConverter converter( pNode->GetFbxManager() );
		
				if ( converter.TriangulateInPlace( pNode ) == FALSE )
				{
					PSX_Assert( FALSE, "Failed to triangulate node." );
					return FALSE;
				}
			}
		}

		// Propage down to each children
		for ( int i = 0; i < pNode->GetChildCount(); ++i )
		{
			TriangulateRecursive( pNode->GetChild( i ) );
		}

		return TRUE;
	}

	EErrorCode::Type FBXUtility::FindMeshToProcess( FbxNode *pNode )
	{
		FbxNodeAttribute *pAttr =  pNode->GetNodeAttribute();

		if ( pAttr )
		{
			if ( pAttr->GetAttributeType() == FbxNodeAttribute::eMesh )
			{
				if ( ProcessMesh( pNode ) == EErrorCode::OKAY )
					return EErrorCode::OKAY;
			}
		}

		// Find other child
		for ( int i = 0; i < pNode->GetChildCount(); ++i )
		{
			if ( FindMeshToProcess( pNode->GetChild(i) ) == EErrorCode::OKAY )
			{
				//return EErrorCode::OKAY;
			}
		}

		return EErrorCode::OKAY;
	}

}